package simulation;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import physicalObjects.Mass;
import physicalObjects.Muscle;
import physicalObjects.Spring;
import forces.Force;


/**
 * Simulates objects moving around in a bounded environment.
 *
 * @author Robert C. Duvall
 * Heavily modified by Michael Elgart
 */
public class Simulation {
    private static final double USER_SPRING_K_VALUE = 0.005;
    private List<Mass> myMasses;
    private List<Spring> mySprings;
    private List<Muscle> myMuscles;
    //private List<PhysicalObj> myObjs;
    private Canvas myContainer;
    private int myNearestMassID;
    private int myNewUserMassID = -1;
    private Spring myUserSpring;
    private boolean myUserInput = false;


    /**
     * Create a Canvas with the given size.
     * @param container The object where the simulation runs
     */
    public Simulation (Canvas container) {
        myMasses = new ArrayList<Mass>();
        mySprings = new ArrayList<Spring>();
        myMuscles = new ArrayList<Muscle>();
        //myObjs = new ArrayList<PhysicalObj>();
        myContainer = container;
    }

    /*public void add (PhysicalObj obj) {
        myObjs.add(obj);
    }*/

    /**
     * Adds a mass.
     * @param mass The mass to be added
     */
    public void add (Mass mass) {
        myMasses.add(mass);
    }

    /**
     * Adds a spring.
     * @param spring The spring to be added.
     */
    public void add (Spring spring) {
        mySprings.add(spring);
    }

    /**
     * Adds a muscle.
     * @param muscle The muscle to be added
     */
    public void add (Muscle muscle) {
        myMuscles.add(muscle);
    }

    /**
     * Paint all shapes on the canvas.
     * @param pen used to paint shape on the screen
     */
    public void paint (Graphics2D pen) {
        for (Muscle ms : myMuscles) {
            ms.paint(pen);
        }
        for (Spring s : mySprings) {
            s.paint(pen);
        }
        for (Mass m : myMasses) {
            m.paint(pen);
        }
    }

    /**
     * Called by each step of timer, multiple times per second.
     *
     * This should update the state of the animated shapes by just
     * a little so they appear to move over time.
     * @param dt The change in time between frames
     */
    public void update (double dt) {
        updateMovers(dt);
    }

    void updateMovers (double dt) {
        if (myUserInput) {
            updateUserMass();
        }
        for (Spring s : mySprings) {
            s.update(this, dt);
        }
        for (Muscle ms : myMuscles) {
            ms.update(this, dt);
        }
        for (Mass m : myMasses) {
            m.update(this, dt);
        }

    }

    /**
     * Changes the location of the fixed mass that the user controls.
     */
    public void updateUserMass () {
        Point mousePosition = myContainer.getLastMousePosition();
        getMass(myNewUserMassID).getCenter().setLocation(
                mousePosition.getX(), mousePosition.getY());
    }

    /**
     * Finds the shortest Distance between the mouse and the closest mass.
     * @param mousePosition Current position of the mouse.
     * @return returns the shortest distance to the closest mass.
     */
    public double findShortestDistance(Point mousePosition) {
        Point2D mousePosition2D =
            new Point2D.Double(mousePosition.getX(), mousePosition.getY());
        double shortestDistance = myContainer.getHeight()
            + myContainer.getWidth();
        for (Mass m: myMasses) {
            if (Force.distanceBetween(mousePosition2D, m.getCenter())
                    < shortestDistance) {
                shortestDistance = Force.distanceBetween(
                        mousePosition2D, m.getCenter());
                myNearestMassID = m.getID();
            }
        }
        return shortestDistance;
    }

    /**
     * Changes the simulation based on user keystrokes.
     * @param keyCode The code of the key pressed.
     */
    public void updateUserInput(int keyCode) {
        switch (keyCode) {
            case KeyEvent.VK_G:
                toggleGravity();
                break;
            case KeyEvent.VK_V:
                toggleViscosity();
                break;
            case KeyEvent.VK_M:
                toggleCenterMass();
                break;
            case KeyEvent.VK_1:
                toggleTopWallForce();
                break;
            case KeyEvent.VK_2:
                toggleRightWallForce();
                break;
            case KeyEvent.VK_3:
                toggleBottomWallForce();
                break;
            case KeyEvent.VK_4:
                toggleLeftWallForce();
                break;
            default:
                break;
        }
    }

    /**
     * Adds the mass and spring that are created by a user click.
     * @param lastMousePosition The Point location of the mouse.
     */
    public void addUserObjects(Point lastMousePosition) {
        myUserInput = true;
        while (getMass(myNewUserMassID) != null) {
            myNewUserMassID += 1;
        }
        Mass myNewUserMass = new Mass(myNewUserMassID,
                lastMousePosition.getX(), lastMousePosition.getY(), -1);
        myMasses.add(myNewUserMass);
        Mass myNearestMass = getMass(myNearestMassID);
        myUserSpring = new Spring(myNewUserMass, myNearestMass,
                Force.distanceBetween(myNewUserMass.getCenter(),
                        myNearestMass.getCenter()), USER_SPRING_K_VALUE);
        mySprings.add(myUserSpring);
    }

    /**
     * Deletes the mass and spring created by the user
     * when the mouse is released.
     */
    public void deleteUserObjects() {
        myUserInput = false;
        myMasses.remove(getMass(myNewUserMassID));
        mySprings.remove(myUserSpring);
    }

    private void toggleLeftWallForce () {
        if (0 == Global.getWallLeftMagnitude()) {
            Global.setWallLeftMagnitude(
                    Global.getWallLeftMagnitudeDefault());
        }
        else {
            Global.setWallLeftMagnitude(Global.getZero());
        }
    }

    private void toggleBottomWallForce () {
        if (0 == Global.getWallBottomMagnitude()) {
            Global.setWallBottomMagnitude(
                    Global.getWallBottomMagnitudeDefault());
        }
        else {
            Global.setWallBottomMagnitude(Global.getZero());
        }
    }

    private void toggleRightWallForce () {
        if (0 == Global.getWallRightMagnitude()) {
            Global.setWallRightMagnitude(
                    Global.getWallRightMagnitudeDefault());
        }
        else {
            Global.setWallRightMagnitude(Global.getZero());
        }
    }

    private void toggleTopWallForce () {
        if (0 == Global.getWallTopMagnitude()) {
            Global.setWallTopMagnitude(
                    Global.getWallTopMagnitudeDefault());
        }
        else {
            Global.setWallTopMagnitude(Global.getZero());
        }
    }

    private void toggleGravity () {
        if (0 == Global.getGravityMagnitude()) {
            Global.setGravityMagnitude(
                    Global.getGravityMagnitudeDefault());
        }
        else {
            Global.setGravityMagnitude(Global.getZero());
        }
    }

    private void toggleCenterMass () {
        if (0 == Global.getCenterMassMagnitude()) {
            Global.setCenterMassMagnitude(
                    Global.getCenterMassMagnitudeDefault());
        }
        else {
            Global.setCenterMassMagnitude(Global.getZero());
        }
    }

    private void toggleViscosity () {
        if (0 == Global.getViscosity()) {
            Global.setViscosity(Global.getViscosityDefault());
        }
        else {
            Global.setViscosity(Global.getZero());
        }
    }


    /**
     * Returns size (in pixels) of the game area.
     */
    public Dimension getSize () {
        return myContainer.getSize();
    }

    /**
     * Retrieve a physical object with the id.
     * @param id The id of the object you are looking for
     * @return Returns the physical object ID'd, null otherwise
     */
    public Mass getMass (int id) {
        for (Mass m : myMasses) {
            if (m.match(id)) {
                return m;
            }
        }
        return null;
    }
}
